Skip to content

Session bootstrap: a probe that never ran, and a venv fix that killed every uv tool - #354

Merged
Jammy2211 merged 9 commits into
mainfrom
claude/mobile-workflow-review-tyv13y
Aug 27, 2026
Merged

Session bootstrap: a probe that never ran, and a venv fix that killed every uv tool#354
Jammy2211 merged 9 commits into
mainfrom
claude/mobile-workflow-review-tyv13y

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

The session/environment half of the 2026-08-27 mobile-workflow pass. Merge after PyAutoLabs/PyAutoBrain#304: firewall_gate.yml pins PyAutoBrain to main and is path-filtered to scripts/repos_sync.py, which this PR changes.

Everything here was measured in this container, not recalled.

The import probe never ran

--check asks whether the interpreter that will run the suite can import pytest, PyYAML and xdist. It said import importlib, which does not bind importlib.util, so the snippet raised AttributeError on every real CPython, the && short-circuited, and the OK line printed. This session's first --check:

[bootstrap] python3: 3.12 OK (/usr/local/bin/python3)
[bootstrap] pytest:  3.12 OK (/root/.local/bin/pytest)

beside a python3 -m pytest answering No module named pytest and a pytest -n auto dying on unrecognized arguments: -n.

The test written to cover exactly this passed throughout: it drove a shell script standing in for python, which answered the fixture's line and could not disagree with the snippet. So the probe is now one string ($IMPORT_PROBE) the suite lifts out and runs on a real interpreter, and a probe that cannot run reports as a failure instead of falling through to OK.

The venv fix broke every uv tool, and --check called them OK

After a clean bootstrap, mypy, flake8, black, poetry and pyright all died with ModuleNotFoundError naming themselves, packages installed two directories away; uv tool list said Failed find package flake8 in tool environment.

uv creates each tool env with bin/python symlinked to whatever python3 was at install time — /usr/local/bin/python3 — and the hook replaces that path with a wrapper that execs the session venv (a wrapper precisely because an earlier pass proved a symlink there loses the venv). The exec replaces argv, so CPython never sees the pyvenv.cfg beside it and resolves sys.prefix to the venv; the tool's own site-packages is off sys.path. ruff survived (native binary), pytest survived (shimmed straight at the venv). Everything else a session lints with was dead — so a remote session has been linting clean by not linting, while CI runs the same tools for real.

repair_uv_tools repoints any tool env whose interpreter does not resolve to its own prefix, verifying the repoint rather than assuming it, and asking sys.prefix rather than tracing the link (the wrapper is invisible to readlink). --check now runs each tool: version was necessary and never sufficient.

The advice was keyed to a symptom that stopped firing

AGENTS.md said to bootstrap if pytest misbehaved. The container now ships 3.12 with the linters on it, so the session looks right and the remaining failure reads like a bad flag. Now unconditional, and it names the leg with teeth: the bootstrap unshallows the clones, without which merge-base --is-ancestor reports merged branches as unmerged.

The block is generated now

policy/remote_sessions.md is the single source, written into each repo's AGENTS.md by repos_sync.py --write with a drift check — the follow-up the previous pass named and deferred, because the per-repo halves (test counts, timings, deps) differed. Those are removed rather than encoded, and a test pins their absence: a number generated into every repo's always-loaded context is a confident wrong answer in the others the moment a suite grows. Opt-in by markers, so PyAutoHeart and PyAutoHands — which this session could not attach — are skipped rather than failing a CI leg on their behalf; filed as draft/maintenance/organs/session_fixes_reach_only_two_organs.md.

Also

The board's gh-less prompt had its Phase 0 probe run ($GH_TOKEN: 200 on /user, 403 on every repo path), which selects the injection seam; the parent is phased and phase 1 is issued as PyAutoLabs/PyAutoBrain#303.

Validation

252 tests green (-n auto), lifecycle.py check and repos_sync.py --check clean, ruff clean on the files touched. Every new guard was confirmed to FAIL against the pre-fix tree first — the uv-tool one needs the wrapper, not a bare symlink, which is why the breakage arrived with the fix that introduced the wrapper.


Generated by Claude Code

claude added 9 commits August 27, 2026 18:01
…m that stopped firing

Two halves of the same defect, both measured in a fresh container today.

The probe: `--check` asks whether the interpreter that will run the suite can
import pytest, PyYAML and xdist. It said `import importlib`, which does not
bind `importlib.util`, so the snippet raised AttributeError on every real
CPython, the `&&` short-circuited, and the OK line printed. This session's
first `--check` reported `python3: 3.12 OK` and `pytest: 3.12 OK` beside a
`python3 -m pytest` answering `No module named pytest` and a `pytest -n auto`
dying on `unrecognized arguments: -n`.

The test that was meant to cover exactly this passed throughout: it drove a
shell script standing in for python, which answered the fixture's line and
could not disagree with the snippet. So the probe is now one string
($IMPORT_PROBE) the suite lifts out and runs on a real interpreter, and a probe
that cannot run is reported as a failure rather than falling through to OK —
"could not ask" and "asked, nothing missing" were indistinguishable, which is
the shape this series keeps finding.

The advice: AGENTS.md told a session to bootstrap *if* pytest misbehaved. The
container now ships 3.12 with pytest, mypy, ruff and flake8 on it, so the
symptom no longer appears and the remaining failure reads like a bad flag. A
remedy keyed to a symptom is only as good as the symptom, so the instruction is
now unconditional: bootstrap before the first test command. ~10s cold, ~1s warm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Pass 1 fixed the source of the ancestry lie — the hook unshallows — but nothing
a session reads says so, and `--check` is the only thing that reports the
state. This session arrived with 78 of PyAutoMind's 4478 commits, so
`--is-ancestor` would have answered "not an ancestor" for merged branches: the
answer the ship and close-out procedures act on.

Recorded beside the bootstrap instruction, since the bootstrap is the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Measured this session, after a clean bootstrap: mypy, flake8, black, poetry and
pyright all died with ModuleNotFoundError naming themselves, with the package
installed two directories away. `uv tool list` said "Failed find package flake8
in tool environment". `--check` reported every one of them `3.12 OK`.

Cause, and it is our own fix's blast radius. uv creates each tool env with
`bin/python` as a symlink to whatever `python3` was at install time —
/usr/local/bin/python3 — and the hook then repoints that path at the session
venv. Crucially it repoints it with a WRAPPER SCRIPT, because an earlier pass
proved a symlink there loses the venv; the wrapper `exec`s the venv's python,
which replaces argv, so CPython never sees the tool env beside it and resolves
sys.prefix to the venv. The tool's own site-packages is then off sys.path.
ruff survived (native binary) and pytest survived (shimmed straight at the
venv). Everything else a session lints with was dead.

So a remote session has been linting clean by not linting at all, while CI runs
the same tools for real — the local-clean/CI-red generator this series exists to
kill, one layer down from where it was last found.

Two changes:

- `repair_uv_tools` (also `--repair-uv-tools`) repoints any tool env whose
  interpreter does not resolve to its own prefix at the base 3.12 interpreter,
  and verifies the repoint rather than assuming it. It asks sys.prefix rather
  than tracing the link, because the wrapper is invisible to readlink.
- `--check` now runs each tool. Version was necessary and never sufficient:
  these tools reached a 3.12 interpreter, just not theirs.

The test that reproduces it needs the wrapper, not a bare symlink — a tool env
whose python merely symlinks elsewhere still finds its own pyvenv.cfg and is
fine. That is why the breakage arrived with the fix that introduced the wrapper.

Left in scripts/ rather than promoted into policy/session_start_hook.sh: the
hook is generated into all four organs and two of them cannot be attached to
this session, so changing it would make firewall_gate red on repos this session
cannot fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
…t miss it

The follow-up pass 3 named and deliberately did not take. Three passes in a row
found a repo whose copy of this guidance had not learned what the previous pass
measured, and one found two organs still shipping a bug a third had fixed.

Pass 3's reason for deferring was that the per-repo halves differ: each copy
named its own test count, its own timings, its own declared deps. Those are
exactly what rots — a number generated into every repo's always-loaded context
is a confident wrong answer in the other three the moment a suite grows — so
they are removed rather than encoded, and a test pins their absence. A repo's
own dependencies stay in its .claude/session-python.txt, where the hook reads
them at run time.

Same shape as the never-rewrite-history policy: one source
(policy/remote_sessions.md), N generated copies, a drift check. Opt-in by
markers, so a repo this session cannot attach is skipped rather than failing a
CI leg on its behalf — PyAutoHeart and PyAutoHands still carry their own text
and are a marker addition away, which is the honest state of the rollout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Both are rollouts that stop at the repos a session can attach. add_repo was
refused for PyAutoHeart and PyAutoHands here, so: their opt-in to the generated
remote-session block, and promoting repair_uv_tools from the bootstrap into the
hook (where a single-repo session, which never calls the bootstrap, would get
it) both need a four-organ session.

Dashboard regenerated in the same commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
…seam

The prompt's Phase 0 is a four-curl probe, and it now has an answer rather than
a plan to get one: $GH_TOKEN reaches /user and /rate_limit (200) and every
repo-scoped path with 403 "GitHub access is not enabled for this session",
unchanged from 2026-08-26. Option 2 needs an org-admin action that has not
happened; option 3 rides the same credential.

That selects option 1 by the prompt's own rule. Written down so the next
session starts at the design rather than at the probe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
`pyauto-brain feature` scored the parent large (9) and returned
split-into-phases. Phase 1 builds the injection seam and proves it on the seven
overnight rows; phase 2 ports the remaining four legs, whose real question —
whether each script grows its own seam — is answered in phase 1's contract.

The parent keeps the design, the measurements and the probe evidence, and is
marked phased so the dashboard stops offering it as pickable work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Phase 1 of the board's gh-less work is issued: PyAutoBrain#303, prompt advanced
draft/ → active/, registered in active.md with no worktree claimed (this is a
remote session, so /start_library runs wherever the task is picked up).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
…w-review-tyv13y

# Conflicts:
#	dashboard.html
#	dashboard.md
@Jammy2211
Jammy2211 merged commit 0124c86 into main Aug 27, 2026
5 checks passed
@Jammy2211
Jammy2211 deleted the claude/mobile-workflow-review-tyv13y branch August 27, 2026 18:35
Jammy2211 pushed a commit that referenced this pull request Aug 27, 2026
…dispatchable

Both close-out PRs this session (#354, #357) showed zero check runs at open, on
diffs matching three path filters plus spawn_drift.yml, which has none. The
2026-08-26 record logged this as a one-off GitHub-side miss; at two occurrences
it belongs in the record as a repeatable condition, with what actually works.

The part that is not obvious and cost a detour: of the four workflows, only
firewall_gate.yml can be dispatched to verify a branch. lifecycle_drift.yml and
dashboard_refresh.yml self-heal main on a manual dispatch — they reset to
origin/main and push — so dispatching them says nothing about the branch and
writes to main. Their commands run locally instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1SLkLDW6aEZCVf95XXeu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants